﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Web;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using RbacLib.Entities;
using RbacLib.Mappers;
using RbacLib.Models;

namespace RbacLib
{
    public class Service
    {
        private const string DisabledButton = "disabled='disabled'";
        private static readonly string EnabledButton = string.Empty;

        private const string HideMenu = "style = 'display: none;'";
        private static readonly string ShowMenu = string.Empty;

        enum Active
        {
            Active = 1, NotActive = 0,
        }

        #region NHibernate
        private ISession _session;
        private ISessionFactory _sessionFactory;

        private void NHibernateConfig()
        {
            //var connectionString = (string)(new AppSettingsReader()).GetValue("connectionString", typeof(string));
            var app = new AppSettingsReader();
            var connectionString = (string)app.GetValue(RbacConstant.ConnectionStringRbac, typeof(string));

            // hack:temp
            var mappingScriptPath = AppDomain.CurrentDomain.BaseDirectory + "/db_mappings";
            if (!Directory.Exists(mappingScriptPath))
                Directory.CreateDirectory(mappingScriptPath);

            _sessionFactory = Fluently.Configure()
                .Database(
                    MsSqlConfiguration.MsSql2008
                        .ConnectionString(@connectionString))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>())
                //.ExposeConfiguration(BuildSchema) // ********** ใช้ครั้งแรกเท่านั้น (กรณียังไม่มี Table)  
                .BuildSessionFactory();
            _session = _sessionFactory.OpenSession();
        }

        private static AutoPersistenceModel CreateAutomappings()
        {
            // This is the actual automapping - use AutoMap to start automapping,
            // then pick one of the static methods to specify what to map (in this case
            // all the classes in the assembly that contains Employee), and then either
            // use the Setup and Where methods to restrict that behaviour, or (preferably)
            // supply a configuration instance of your definition to control the automapper.
            return AutoMap.AssemblyOf<User>()
                            .Conventions.Add<CascadeConvention>()
                            .Where(t => t.Namespace != null && t.Namespace.EndsWith("Entities"));
        }

        private static void BuildSchema(Configuration config)
        {
            new SchemaExport(config).Create(false, true);
        }

        #endregion

        public static int ProjectId;
        private const int RbacProjectId = 0;

        private Service()
        {
            NHibernateConfig();
        }

        public Service(int projectId)
            : this()
        {
            ProjectId = projectId;
        }

        public ResponseDto<UserProfile> Login(LoginCriteria criteria)
        {
            var result = new ResponseDto<UserProfile>();
            try
            {
                using (_session)
                {
                    var user = _session.CreateQuery(@"
                                from User u 
                                where u.Username = :username
                                and u.Password = :password
                                and u.IsActive = :isActive")
                                .SetParameter("username", criteria.Username)
                                .SetParameter("password", criteria.Password)
                                .SetParameter("isActive", true)
                                .UniqueResult<User>();

                    if (user != null)
                    {

                        result.Data = new UserProfile { Username = user.Username, };

                        var roleUser = _session.CreateQuery(@"from RoleUser ru where ru.User = :user")
                                     .SetParameter("user", user).UniqueResult<RoleUser>();

                        if (roleUser != null && roleUser.Role != null)
                        {
                            AutoMapper.Mapper.CreateMap<Role, RoleModel>();
                            AutoMapper.Mapper.CreateMap<Permission, PermissionModel>();
                            AutoMapper.Mapper.CreateMap<Function, FunctionModel>();
                            AutoMapper.Mapper.CreateMap<Domain, DomainModel>();

                            result.Data.Role = AutoMapper.Mapper.Map<Role, RoleModel>(roleUser.Role);
                        }
                    }

                    result.Status = true;
                }
            }
            catch (Exception ex)
            {
                result.Status = false;
                result.ErrorMessage = ex.Message;
            }

            return result;
        }

        public static ResponseDto<HtmlString> PrivilageClass(PrivilegeClassCriteria criteria)
        {
            var result = new ResponseDto<HtmlString> { Data = new HtmlString(HideMenu) };
            try
            {
                const string msgError = RbacConstant.NoPrivilageBeNull;
                if (criteria == null)
                    throw new Exception(string.Format(msgError, RbacConstant.Criteria));

                if (criteria.UserProfile == null)
                    throw new Exception(string.Format(msgError, RbacConstant.UserProfile));

                if (criteria.UserProfile.Role == null)
                    throw new Exception(string.Format(msgError, RbacConstant.Role));

                if (criteria.UserProfile.Role.Permissions == null || criteria.UserProfile.Role.Permissions.Count == 0)
                    throw new Exception(string.Format(msgError, RbacConstant.Permission));

                var privilege = criteria.UserProfile.Role.Permissions
                                .Where(p => p.IsActive == (int)Active.Active)
                                .Where(p => p.Function != null)
                                .Where(p => p.Function.Domain != null)
                                .Where(p => p.Function.Domain.DomainName.ToLower() == criteria.DomainName.ToLower())
                                .Any(p => p.Function.FunctionName == (int)RbacEnum.Function.Access);

                result.Data = privilege ? new HtmlString(ShowMenu) : new HtmlString(HideMenu);
                result.Status = true;
            }
            catch (Exception ex)
            {
                result.Status = false;
                result.ErrorMessage = ex.Message;
            }

            return result;
        }

        public static ResponseDto<string> PrivilegeFunction(PrivilegeFunctionCriteria criteria)
        {
            var result = new ResponseDto<string> { Data = DisabledButton };
            try
            {
                var privilegeClass = ShowMenu == PrivilageClass(new PrivilegeClassCriteria { DomainName = criteria.DomainName, UserProfile = criteria.UserProfile }).Data.ToString();
                var privilege = false;

                if (privilegeClass)
                {
                    privilege = criteria.UserProfile.Role.Permissions
                                .Where(p => p.IsActive == (int)Active.Active)
                                .Where(p => p.Function != null)
                                .Where(p => p.Function.Domain != null)
                                .Where(p => p.Function.Domain.DomainName.ToLower() == criteria.DomainName.ToLower())
                                .Any(p => p.Function.FunctionName == criteria.FunctionName);
                }

                result.Data = privilegeClass && privilege ? EnabledButton : DisabledButton;
                result.Status = true;
            }
            catch (Exception ex)
            {
                result.Status = false;
                result.ErrorMessage = ex.Message;
            }

            return result;
        }

        public static List<string> GetAllClass(int projectId)
        {
            //var app = new AppSettingsReader();
            //var connectionStringCodeGen = (string)app.GetValue(RbacConstant.ConnectionStringCodeGen, typeof(string));

            ////const string connectionString = "Data Source=BUYCHU-PC;Initial Catalog=BeforeGen;User ID=wichan;Password=1234";
            //var queryString = string.Format("SELECT distinct Name FROM ClassModel where Project_id = {0}", projectId);
            //List<string> classes;
            //classes = ProjectModel.GetClassNameByProjectId(projectId);
            //using (var connection = new SqlConnection(connectionStringCodeGen))
            //{
            //    var command = new SqlCommand(queryString, connection);
            //    connection.Open();
            //    var reader = command.ExecuteReader();
            //    try
            //    {
            //        while (reader.Read())
            //        {
            //            classes.Add((string)reader["Name"]);
            //        }
            //    }
            //    finally
            //    {
            //        reader.Close();
            //    }
            //}

            /*@insert_class*/

            //classes.Add("Customer");
            //classes.Add("Employee");
            //classes.Add("EmployeeType");
            //classes.Add("Branch");

            return ProjectModel.GetClassNameByProjectId(projectId);
        }

        private bool ExistRbacDomain(int projectId)
        {
            bool result;

            try
            {
                var domains = _session.CreateQuery(@"from Domain d where d.ProjectId = :projectId")
                    .SetParameter("projectId", projectId)
                    .List<Domain>().ToList();
                result = domains.Count > 0;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

            return result;
        }

        private void DeleteDomainByProjectId(int projectId)
        {
            try
            {
                var row = _session.CreateQuery(@"DELETE RbacLib.Entities.Permission where ProjectId = :projectId")
                    .SetParameter("projectId", projectId)
                    .ExecuteUpdate();

                row = _session.CreateQuery(@"DELETE RbacLib.Entities.RoleUser where ProjectId = :projectId")
            .SetParameter("projectId", projectId)
            .ExecuteUpdate();

                row = _session.CreateQuery(@"DELETE RbacLib.Entities.Function where ProjectId = :projectId")
               .SetParameter("projectId", projectId)
               .ExecuteUpdate();

                row = _session.CreateQuery(@"DELETE RbacLib.Entities.Domain where ProjectId = :projectId")
               .SetParameter("projectId", projectId)
               .ExecuteUpdate();

                //                row = _session.CreateQuery(@"DELETE RbacLib.Entities.User where ProjectId = :projectId")
                //.SetParameter("projectId", projectId)
                //.ExecuteUpdate();

                row = _session.CreateQuery(@"DELETE RbacLib.Entities.Role where ProjectId = :projectId")
          .SetParameter("projectId", projectId)
          .ExecuteUpdate();



                //if (row == 0)
                //    throw new Exception("Delete Not Success");

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        #region Auto-Generate
        private const string Description = "Auto-Generate";

        /*
                private enum RbacDomainBuz
                {
                    Employee, Customer, Branch, EmployeeType, BranchType, Evaluation, Department, EvaluationType
                }
        */

        private enum RbacDomain
        {
            User, Role, Permission, Domain, Function, UserRole
        }

        private enum RbacFunction
        {
            Access = 1, Add = 2, Update = 3, Delete = 4, Search = 5
        }

        public void AutoGenerate()
        {
            GenerateDomain();
            GenerateUser();
        }

        private void GenerateDomain()
        {
            using (_session.BeginTransaction())
            {
                try
                {
                    #region Generate RBAC Domains
                    if (!ExistRbacDomain(RbacProjectId))
                    {
                        GenerateRbacDomain();
                    }
                    #endregion

                    #region Generate Business Domains
                    GenerateBusinessDomain();
                    #endregion

                    _session.Transaction.Commit();
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
        }

        private void GenerateBusinessDomain()
        {
            DeleteDomainByProjectId(ProjectId);

            var domainNames = GetAllClass(ProjectId);
            var domains = new List<Domain>();
            foreach (var domainName in domainNames)
            {
                var domain = new Domain
                {
                    CreateDate = DateTime.Now,
                    Description = Description,
                    DomainName = domainName,
                    ProjectId = ProjectId,
                    UpdateBy = null,
                    UpdateDate = DateTime.Now,
                    Version = 0,
                };

                _session.Save(domain);
                domains.Add(domain);
            }

            foreach (var domain in domains)
            {
                GenerateFunctionsByDomain(domain, ProjectId);
            }
        }

        private void GenerateRbacDomain()
        {
            var domains = new List<Domain>();
            foreach (var rbacDomain in (RbacDomain[])Enum.GetValues(typeof(RbacDomain)))
            {
                var domainName = rbacDomain.ToString();
                var domain = new Domain
                {
                    CreateDate = DateTime.Now,
                    Description = Description,
                    DomainName = domainName,
                    ProjectId = RbacProjectId,
                    UpdateBy = null,
                    UpdateDate = DateTime.Now,
                    Version = 0,
                };

                _session.Save(domain);
                domains.Add(domain);
            }

            foreach (var domain in domains)
            {
                GenerateFunctionsByDomain(domain, RbacProjectId);
            }
        }

        private void GenerateFunctionsByDomain(Domain domain, int projectId)
        {
            if (domain == null) return;

            var functions = new List<Function>();
            var className = domain.DomainName;

            try
            {
                foreach (var func in (RbacFunction[])Enum.GetValues(typeof(RbacFunction)))
                {
                    var name = func.ToString();
                    var funcName = string.Format("{0}_{1}", name, className).ToUpper();
                    //var description = string.Format("To privilege {0} {1} class.", name, className);
                    var readonlyDomain = (RbacDomain.Domain.ToString() == domain.DomainName || RbacDomain.Function.ToString() == domain.DomainName);
                    var readonlyFunction = (RbacFunction.Add == func || RbacFunction.Update == func || RbacFunction.Delete == func);
                    var isActive = !((readonlyDomain) && (readonlyFunction));
                    var function = new Function
                    {
                        CreateDate = DateTime.Now,
                        Description = Description,
                        Domain = domain,
                        FuncName = funcName,
                        FunctionName = (int)func,
                        ProjectId = projectId,
                        UpdateBy = null,
                        UpdateDate = DateTime.Now,
                        Version = 0,
                    };

                    _session.Save(function);
                    functions.Add(function);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        private void GenerateUser()
        {
            try
            {
                using (_session.BeginTransaction())
                {
                    #region Role
                    var role = new Role
                    {
                        CreateDate = DateTime.Now,
                        Description = Description,
                        RoleCode = "ADMIN",
                        RoleName = "Administrator",
                        ProjectId = ProjectId,
                        IsActive = (int)Active.Active,
                        UpdateBy = null,
                        UpdateDate = DateTime.Now,
                        Version = 0,
                    };
                    _session.Save(role);
                    #endregion

                    #region Permission
                    var functions = _session.QueryOver<Function>().List();
                    foreach (var function in functions)
                    {
                        var permissionName = string.Format("{0}_{1}", role.RoleCode, function.FuncName);
                        var permission = new Permission
                        {
                            CreateDate = DateTime.Now,
                            Description = Description,
                            Function = function,
                            PermissionName = permissionName,
                            Role = role,
                            ProjectId = ProjectId,
                            IsActive = (int)Active.Active,
                            UpdateBy = null,
                            UpdateDate = DateTime.Now,
                            Version = 0,
                        };
                        _session.Save(permission);
                    }
                    #endregion

                    #region User

                    var user = new User
                    {
                        CreateDate = DateTime.Now,
                        Email = string.Empty,
                        Mobile = string.Empty,
                        Password = "p@ssword",
                        Username = "Administrator",
                        IsActive = (int)Active.Active,
                        UpdateBy = null,
                        UpdateDate = DateTime.Now,
                        Version = 0,
                    };
                    _session.Save(user);

                    var roleUser = new RoleUser
                                       {
                                           CreateDate = DateTime.Now,
                                           ProjectId = ProjectId,
                                           Role = role,
                                           UpdateBy = null,
                                           UpdateDate = DateTime.Now,
                                           User = user,
                                           Version = 0,
                                       };
                    _session.Save(roleUser);
                    #endregion

                    _session.Transaction.Commit();
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        #endregion
    }
}
